package org.lc.cmd;

import ch.qos.logback.core.util.FileUtil;
import org.lc.cmd.impl.TerminalRunner;
import org.lc.cmd.impl.bridge.HttpClientBridge;
import org.lc.cmd.impl.bridge.HttpServerBridge;
import org.lc.cmd.impl.bridge.StdioHttpClientBridge;
import org.lc.cmd.model.CmdRunType;
import org.lc.cmd.model.Pair;
import org.lc.cmd.util.CollectionUtil;
import org.lc.cmd.util.ProcessUtil;
import org.lc.cmd.util.StringUtil;
import org.slf4j.bridge.SLF4JBridgeHandler;
import org.springframework.util.Assert;
import org.springframework.util.FileSystemUtils;

import java.io.File;
import java.io.IOException;
import java.util.Optional;
import java.util.function.Consumer;

public abstract class Cmd {
    private static final String RUN_TYPE = "--cmdRunType";

    private static final String SERVER_URL = "--cmdServer";

    private static final String SERVER_LOG_FILE = "--cmdServerLogFile";

    protected String[] args;

    public static Cmd INSTANCE;

    /**
     * 运行Cmd框架
     * 在某些情况（例如CLIENT_ONCE时，不会拉起Spring框架）
     *
     * @param springInit Spring加载函数，一般是调用{@link org.springframework.boot.SpringApplication#run}
     */
    public abstract void run(Consumer<String[]> springInit) throws IOException;

    public abstract CmdDataBridge getBridge();

    public static Cmd build(String[] args) {
        Pair<String, String[]> pair = findArgAndRemove(args, RUN_TYPE);
        CmdRunType type = Optional.ofNullable(pair.getFirst()).map(CmdRunType::valueOf).orElse(CmdRunType.OVERALL);
        args = pair.getSecond();
        pair = findArgAndRemove(args, SERVER_URL);
        String serverUrl = pair.getFirst();
        args = pair.getSecond();
        pair = findArgAndRemove(args, SERVER_LOG_FILE);
        String logFile = pair.getFirst();
        args = pair.getSecond();
        Cmd cmd = null;
        switch (type) {
            case OVERALL:
                resetCmdLog();
                cmd = TerminalRunner.overAllRunner();
                break;
            case SERVER:
                cmd = new ServerCmd();
                break;
            case CLIENT:
                Assert.hasLength(serverUrl, "需传入命令行参数：" + SERVER_URL);
                cmd = TerminalRunner.httpRunner(serverUrl);
                startServer(serverUrl, args, (HttpClientBridge) cmd.getBridge(), logFile);
                break;
            case CLIENT_ONCE:
                Assert.hasLength(serverUrl, "需传入命令行参数：" + SERVER_URL);
                cmd = new ArgsClientCmd(args, serverUrl);
                startServer(serverUrl, args, (HttpClientBridge) cmd.getBridge(), logFile);
                break;
        }
        cmd.args = args;
        INSTANCE = cmd;
        return cmd;
    }

    private static Pair<String, String[]> findArgAndRemove(String[] args, String argName) {
        String value = null;
        for (int i = 0; i < args.length; i++) {
            String arg = args[i];
            if (arg.startsWith(argName)) {
                value = arg.substring(argName.length() + 1);
                args = CollectionUtil.withOutItem(args, i);
                break;
            }
        }
        return new Pair<>(value, args);
    }

    private static void resetCmdLog() {
        SLF4JBridgeHandler.removeHandlersForRootLogger();
        SLF4JBridgeHandler.install();
    }

    private static void startServer(String serverUrl, String[] args, HttpClientBridge bridge, String logFile) {
        File file;
        if (StringUtil.isNotEmpty(logFile)) {
            file = new File(logFile);
            FileUtil.createMissingParentDirectories(file);
        } else {
            file = new File(ProcessUtil.isWindows() ? "NIL" : "/dev/null");
        }

        String[] serverArgs = new String[args.length + 1];
        System.arraycopy(args, 0, serverArgs, 0, args.length);
        serverArgs[args.length] = RUN_TYPE + "=SERVER";
        bridge.connectServer(serverUrl, serverArgs, file);
    }

    private static class ServerCmd extends Cmd {
        private final CmdDataBridge bridge = new HttpServerBridge();

        @Override
        public void run(Consumer<String[]> springInit) {
            springInit.accept(args);
        }

        @Override
        public CmdDataBridge getBridge() {
            return bridge;
        }
    }

    private static class ArgsClientCmd extends Cmd {
        private final CmdDataBridge bridge;

        private final String[] args;

        private ArgsClientCmd(String[] args, String serverUrl) {
            this.args = args;
            bridge = new StdioHttpClientBridge(serverUrl);
        }


        @Override
        public void run(Consumer<String[]> springInit) throws IOException {
            bridge.sendCmdLine(buildCmd(args));
        }

        @Override
        public CmdDataBridge getBridge() {
            return bridge;
        }

        private String buildCmd(String[] args) {
            if (args == null || args.length == 0) {
                return "";
            }

            StringBuilder cmdBuilder = new StringBuilder();
            for (String arg : args) {
                // Escape special characters
                arg = arg.replaceAll("([\"\\\\$`])", "\\\\$1");
                cmdBuilder.append('"').append(arg).append("\" ");
            }

            return cmdBuilder.toString().trim();
        }

    }
}
